#include <iostream>

#include <cstdio>
#include <cstring>

#include <string>
#include <vector>
#include <unordered_map>

#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <functional>

//类似于函数指针，要传递一个返回值为std::string，参数为const std::string的函数
using func_t = std::function<std::string(const std::string, const std::string, const uint16_t)>;
// typedef std::function<std::string(const std::string)> func_t;

//注意端口号如果被绑定的话，bind是无法成功的，netstat -nulp查看
const uint16_t defaultport = 8080;
const std::string defaultip = "127.0.0.1";

const unsigned int size = 1024;

enum
{
    SOCK_ERR = 1,
    BIND_ERR
};

class UdpServer
{
public:
    UdpServer()
    :_sockfd(0), _port(defaultport), _ip(defaultip)
    {}
    void Init()
    {
        //创建socket文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd < 0)
        {
            std::cerr << "socket fail" << std::endl;
            exit(SOCK_ERR);
        }

        //初始化套接字
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());
        //绑定ip和端口号
        int flag = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(flag < 0)
        {
            std::cerr << "bind fail" << std::endl;
            exit(BIND_ERR);
        }
    }
    //维护用户列表
    void CheckUser(const struct sockaddr_in& client, const std::string& clientip, const uint16_t& clientport)
    {
        auto iter = _online_user.find(clientip);
        if(iter == _online_user.end())
        {
            _online_user.insert({clientip, client});
            std::cout << "[ip:" << clientip << "  port:" << clientport << "] add online_user" << std::endl;
        }
    }
    //多人聊天处理数据
    void Broadcast(const std::string& info, const std::string& clientip, const uint16_t& clientport)
    {
        std::cout << "用户列表：" << std::endl;
        for (std::unordered_map<std::string, const struct sockaddr_in>::iterator it = _online_user.begin(); it != _online_user.end(); ++it) 
        {  
            std::cout << "Key: " << it->first << std::endl;  
        }  

        for(const auto& user : _online_user)
        {
            std::string message = "[";
            message += clientip;
            message += ";";
            message += std::to_string(clientport);
            message += "]#";
            message += info;
            sendto(_sockfd, message.c_str(), message.size(), 0, (const struct sockaddr*)(&user.second), sizeof(user.second));
        }
    }

    void Run(func_t func)
    {
        while(true)
        {
            //接收数据、同时记录client信息
            char inbuffer[size];
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            //1.Udp通信是面向数据报的，recvfrom和sendto也是，不用read和write是因为他俩面向字节流
            //2.recvfrom将读取到的数据放入inbuffer中，同时记录client的ip、port等，以便后续回发数据
            //3.如果读取失败，则返回0，读取成功，返回读取到的字节长度
            //4.inbuffer长度-1是为了存放字符串结尾标志'\0'
            //5.如果inbuffer里面有数据的话，会读取新来的数据进行覆盖
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer)- 1 , 0, (struct sockaddr*)&client, &len);
            if(n < 0) continue;
            
            //获取client的ip和端口号
            const std::string clientip = inet_ntoa(client.sin_addr);
            const uint64_t clientport = ntohs(client.sin_port);

            //处理数据(网络多人聊天版本)
            inbuffer[n] = 0;
            std::string info = inbuffer;
            CheckUser(client, clientip, clientport);
            
            //回发数据
            Broadcast(info, clientip, clientport);
        }
    }
    ~UdpServer()
    {
        if(_sockfd > 0) close(_sockfd);
    }

private:
    int _sockfd;
    uint16_t _port;
    const std::string _ip;
    std::unordered_map<std::string, const struct sockaddr_in> _online_user;
};


//---------------------------------------------------------------------------------------
/**
 * 改进：
 * 1.将获取网络数据和处理数据的过程分层解耦 --- 在Run函数中调用udp_server.cc中的Handler函数
*/

/**
 * （应用场景1）传命令
 * 1.client发的是命令，server处理命令，并将结果返回给client
 * 2.需要对client发的命令就行安全检测等处理
*/

/**
 * （应用场景2）windows和linux之间的通信
 * 1.linux充当服务器，windows为客户端
*/

/**
 * (应用场景3）聊天软件的简单模拟
 * 1.用ip和port模拟用户昵称 
 * 2.用unorder_map维护一张用户信息表
 * 3.当服务器端接受到数据后，通过用户信息表，逐个转发给所有用户
 * 4.Udp是全双工的，所以一个套接字文件可以一边读一边写，所以可以使用多线程，服务器和客户端都可以一边读一边写
 *   客户端多线程化：否则的话，不发送信息会在getline上卡住，无法收到消息
 * （多线程，makefile要加入-lpthread)
*/